using System;
using System.Collections.Generic;
using System.IO;
using Microsoft.Office.Core;
using Microsoft.Office.Interop.Word;

namespace WordConvert
{
    class Word
    {
        private Application _application;

        public Word ()
	    {
            _application = new Application();
	    }

        #region IDisposable Members
        ~Word()
        {
            Dispose(false);
        }

        private bool _disposed = false;

        public Application Application
        {
            get
            {
                VerifyNotDisposed();
                return this._application;
            }
        }

        public void Dispose()
        {
          Dispose(true);
          GC.SuppressFinalize(this);
        }
        		
        protected virtual void Dispose(bool disposing)
        {
          if (!this._disposed)
          {
            if (disposing)
            {
              // dispose-only, i.e. non-finalizable logic

            }
            this._disposed = true;
            // shared (dispose and finalizable) cleanup logic
            if (_application != null)
            {
                object falseObj = false;
                object nullObj = Type.Missing;
                ((_Application)_application).Quit(ref falseObj /*save Changes*/,
                                      ref nullObj /*original format*/,
                                      ref nullObj /*route document*/);
//                Marshal.ReleaseComObject(_application);
                _application = null;
            }
          }
        }

        protected void VerifyNotDisposed(){
          if (this._disposed)
          {
              throw new ObjectDisposedException("WordApplication");
          }
        }
        #endregion
    }

    class Program
    {
        private static Word _word;

        static int Main(string[] args)
        {
            int errorCode = 0;
            if(args.Length < 3)
            {
                WriteUsage();
                errorCode = 1;
            }

            Document document = null;
            if (errorCode == 0)
            {
                _word = new Word();
                try
                {
                    document = OpenDocument(args[0]);
                }
                catch (Exception e)
                {
                    Console.WriteLine("Unable to open file " + args[0]);
                    Console.WriteLine("Details:");
                    Console.WriteLine(e.Message + '\n' + e);
                    errorCode = 2;
                }
            }

            if (errorCode == 0)
            {
                try
                {
                    SaveDocumentAs(document, args[1], args[2], (args.Length > 3)? args[3]:null);
                }
                catch (Exception e)
                {
                    Console.WriteLine("Unable to save file " + args[1]);
                    Console.WriteLine("Details:");
                    Console.WriteLine(e.Message);
                    errorCode = 3;
                }
                finally
                {
                    CloseDocument(document);
//                    Marshal.ReleaseComObject(document);
                }
            }

            GC.Collect();
            GC.WaitForPendingFinalizers();

            GC.Collect();
            GC.WaitForPendingFinalizers();
            return errorCode;
        }

        private static Document OpenDocument(string fileName)
        {
            object fileNameObj = Path.GetFullPath(fileName);
            object trueObj = true;
            object falseObj = false;
            object nullObj = Type.Missing;

            return _word.Application.Documents.Open(ref fileNameObj,
                                        ref falseObj  /*confirm conversions*/,
                                        ref trueObj /*readonly*/,
                                        ref falseObj /*add to recent files*/,
                                        ref nullObj /*password for opening the document*/,
                                        ref nullObj /*password for opening the template*/,
                                        ref nullObj /*revert changes if already open*/,
                                        ref nullObj /*password for saving the document*/,
                                        ref nullObj /*password for saving the template*/,
                                        ref nullObj /*file converter*/,
                                        ref nullObj /*encoding*/,
                                        ref falseObj /*visible*/,
                                        ref falseObj /*repair*/,
                                        ref nullObj /*document direction*/,
                                        ref trueObj /*don't show encoding dialog*/,
                                        ref nullObj /*xml transform to use*/);
        }

        private static void CloseDocument(_Document document)
        {
            object falseObj = false;
            object nullObj = Type.Missing;
            document.Close(ref falseObj/*save changes*/, 
                           ref nullObj/*original format*/, 
                           ref nullObj/*route document*/);
        }

        private static void SaveDocumentAs(Document document, string fileName, string format, string encoding)
        {
            string saveFormatAsString = "wdFormat" + format;
            WdSaveFormat saveFormat;
            try
            {
                saveFormat = (WdSaveFormat)Enum.Parse(typeof(WdSaveFormat), saveFormatAsString);
            }
            catch(ArgumentException)
            {
                string errorMessage = "Bad Format Value. Must be one of:\n";
                foreach (string s in GetAvailableFormats())
                {
                    errorMessage += '\t' + s +'\n';
                }
                throw new ArgumentException(errorMessage);
            }

            string encodingAsString = "msoEncoding" +
                                      (String.IsNullOrEmpty(encoding) ? "UnicodeLittleEndian" : encoding);
            MsoEncoding saveEncoding;
            try
            {
                saveEncoding = (MsoEncoding) Enum.Parse(typeof (MsoEncoding), encodingAsString);
            }
            catch(ArgumentException)
            {
                string errorMessage = "Bad Encoding Value. Must be one of:\n";
                foreach (string s in GetAvailableEncodings())
                {
                    errorMessage += '\t' + s + '\n';
                }
                throw new ArgumentException(errorMessage);
            }

            object fileNameObj = Path.GetFullPath(fileName);
            object saveFormatObj = saveFormat;
            object falseObj = false;
            object nullObj = Type.Missing;
            object encodingObj = saveEncoding;

            document.SaveAs(ref fileNameObj, ref saveFormatObj,
                ref falseObj/*lock comments*/,
                ref nullObj/*password*/,
                ref falseObj/*add to recent*/,
                ref nullObj/*write password*/,
                ref nullObj/*read only recommended*/,
                ref nullObj/*embed true-type fonts*/,
                ref nullObj/*save native picture format*/,
                ref nullObj/*save forms data*/,
                ref nullObj/*save as AOCE Letter*/,
                ref encodingObj/*encoding*/,
                ref nullObj/*insert line breaks*/,
                ref nullObj/*allow substitutions*/,
                ref nullObj/*line ending*/,
                ref nullObj/*add bidi marks*/);
        }

        private static void WriteUsage()
        {
            Console.WriteLine("Usage: WordConvert Input Output Format [Encoding]");
            Console.WriteLine();
            Console.WriteLine("Available formats:");

            foreach (string format in GetAvailableFormats())
            {
                Console.WriteLine('\t' + format);
            }
            Console.WriteLine("Available Encodings:");

            foreach (string format in GetAvailableEncodings())
            {
                Console.WriteLine('\t' + format);
            }
        }

        private static IList<string> GetAvailableFormats() {
            List<string> formats = new List<string>();
            foreach (string saveFormat in Enum.GetNames(typeof(WdSaveFormat)))
            {
                formats.Add(saveFormat.Substring(8));
            }
            return formats;
        }

        private static IList<string> GetAvailableEncodings()
        {
            List<string> formats = new List<string>();
            foreach (string encoding in Enum.GetNames(typeof(MsoEncoding)))
            {
                formats.Add(encoding.Substring(11));
            }
            return formats;
        }
    }
}
